#include "common.h"
#include "glwindow.h"
#include "gputracer.h"
#include "minterface.h"

GPUTracer  *g_pTracer;
MetamorphInterface * g_pInterface;

int g_iPrevPointX = -12345, g_iPrevPointY = -12345;
bool bLeftMouse = false;
bool bRightMouse = false;
bool bMiddleMouse = false;

std::string szEdgeFragSource;
std::string szEdgeVertSource;

// GLUT display function
void display()
{
	g_pTracer->update();

	g_pInterface->render();

	glutSwapBuffers();
}

void processKeyboard(unsigned char key, int x, int y)
{
	g_pInterface->onButtonPressed(key);
}

void processKeyboardUp(unsigned char key, int x, int y)
{
	g_pInterface->onButtonReleased(key);
}

void processMouse(int button, int state, int x, int y)
{
	if (state == GLUT_UP)
	{
		switch (button)
		{
		case GLUT_LEFT_BUTTON: bLeftMouse = false; break;
		case GLUT_RIGHT_BUTTON: bRightMouse = false; break;
		case GLUT_MIDDLE_BUTTON: bMiddleMouse = false; break;
		}
		if (x > TRACER_SIZE)
			g_pInterface->onMouseReleased(x, y);
	}
	if (state == GLUT_DOWN)
	{
		g_iPrevPointX = -12345;
		switch (button)
		{
		case GLUT_LEFT_BUTTON: bLeftMouse = true; break;
		case GLUT_RIGHT_BUTTON: bRightMouse = true; break;
		case GLUT_MIDDLE_BUTTON: bMiddleMouse = true; break;
		}
		if (x > TRACER_SIZE)
			g_pInterface->onMousePressed(x, y);
	}
}
void processMouseActiveMotion(int x, int y)
{
	//mouse move, something pressed
	if (g_iPrevPointX == -12345)
	{
		g_iPrevPointX = x;
		g_iPrevPointY = y;
		return;
	}
	if (bLeftMouse)
	{
		float dx = (float)(x - g_iPrevPointX);
		float dy = (float)(y - g_iPrevPointY);
		g_pTracer->rotateX(dy/2);
		g_pTracer->rotateY(dx/2);
		g_iPrevPointX = x;
		g_iPrevPointY = y;
		glutPostRedisplay();
	}
	if (bRightMouse)
	{
		float dx = (float)(x - g_iPrevPointX);
		float dy = (float)(y - g_iPrevPointY);
		g_pTracer->zoom(dy/2);
		g_iPrevPointX = x;
		g_iPrevPointY = y;
		glutPostRedisplay();
	}
}

void processMousePassiveMotion(int x, int y)
{
	//mouse move, nothing pressed
}

void processMouseEntry(int state)
{
}

// GLUT idle function
void idle()
{
	glutPostRedisplay();
}

// GLUT reshape function
void reshape(int w, int h)
{
	if (h == 0) h = 1;

	glViewport(0, 0, w, h);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(-1, 1, -1, 1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

// Called at startup
void initGL()
{
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHT1);
	glEnable(GL_NORMALIZE);
	float position1[] = {40, -10, -20, 1};
	float position2[] = {-20, 10, -20, 1};
	float white[] = {1,1,1,1};
	float yellow[] = {1,1,0,1};
	float light1[] = {255./256., 242./256., 230./256., 1};
	float light2[] = {179./256., 143./256., 17./256., 1};
	float gray[] = {0.6f,0.6f,0.6f,1};
	glLightfv(GL_LIGHT0, GL_POSITION, position1);
	glLightfv(GL_LIGHT0, GL_AMBIENT, white);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light1);
	glLightfv(GL_LIGHT0, GL_SPECULAR, light1);
	glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 5);
	glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0);
	glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0);

	glLightfv(GL_LIGHT1, GL_POSITION, position2);
	glLightfv(GL_LIGHT1, GL_AMBIENT, white);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, gray);
	glLightfv(GL_LIGHT1, GL_SPECULAR, gray);
	glLightf(GL_LIGHT1, GL_CONSTANT_ATTENUATION, 4);
	glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 0);
	glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 0);

	GLfloat mShininess[] = {8};

	GLfloat SpecularMaterial[] = {176./256., 176./256., 176./256}; 
	GLfloat DiffuseMaterial[] = {147./256., 147./256., 147./256.};
	GLfloat AmbientMaterial[] = {170./176., 170./176., 170./176.}; 

	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, DiffuseMaterial);
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, AmbientMaterial);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, SpecularMaterial);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mShininess);	

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
}

void initGlew()
{
	glewInit();

	if (glewGetExtension("GL_ARB_fragment_shader")      != GL_TRUE ||
		glewGetExtension("GL_ARB_vertex_shader")        != GL_TRUE ||
		glewGetExtension("GL_ARB_shader_objects")       != GL_TRUE ||
		glewGetExtension("GL_ARB_texture_rectangle")    != GL_TRUE ||
		glewGetExtension("GL_ARB_shading_language_100") != GL_TRUE)
	{
		MessageBox(NULL, "Driver does not support OpenGL Shading Language", "Error", MB_OK);
		exit(1);
	}
}

void initGlut()
{
    int argc = 1;
    char argvc[] = "null";
    char * argv = argvc;
    glutInit(&argc, &argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowSize(TRACER_SIZE+INTERFACE_SIZE, TRACER_SIZE);
	glutCreateWindow("Wait a minute...");

	glutIdleFunc(idle);
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);

	glutMouseFunc(processMouse);
	glutKeyboardFunc(processKeyboard);
	glutKeyboardUpFunc(processKeyboardUp);
	glutMotionFunc(processMouseActiveMotion);
	glutPassiveMotionFunc(processMousePassiveMotion);
	glutEntryFunc(processMouseEntry);
}

void visualisation(const char* pShader)
{
	initGlut();
	initGlew();

	// Create the example object
	g_pTracer = new GPUTracer(TRACER_SIZE, TRACER_SIZE);
	g_pTracer->setSource(std::string(pShader));
	g_pTracer->init();

	// Create interface
	g_pInterface = new MetamorphInterface();
	g_pInterface->init();

	initGL();

	glutMainLoop();
}

void printInfoLog(GLhandleARB obj)
{
	int infologLength = 0;
	int charsWritten  = 0;
	char *infoLog;

	glGetObjectParameterivARB(obj, GL_OBJECT_INFO_LOG_LENGTH_ARB,
		&infologLength);

	if (infologLength > 0)
	{
		infoLog = (char *)malloc(infologLength);
		glGetInfoLogARB(obj, infologLength, &charsWritten, infoLog);
		MessageBox(NULL, infoLog, "Info log", MB_OK);
		printf(infoLog);
		free(infoLog);
	}
}
